<?php
/**
 *  Copyright (C) 2010  Alvaro Villalba <villalba.it@gmail.com>
 *  Url: http://code.google.com/p/form-element-multidate/
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

require_once 'Zend/Form/Element/Multi.php';

/*
 * Multidate Form Element:
 * 
 * Consist of 3 Zend_Form_Element_MultiSelect, for day, month and year
 */
class MyZend_Form_Element_Multidate extends Zend_Form_Element_Multi
{
	/**
	 * Uses formMultidate view helper by default
	 * @var string
	 */
    public $helper = 'formMultidate';

	/**
     * Names aren't arrays but day, month, year
	 * @var bool
	 */
    protected $_isArray = false;
	
	/**
	 * Which components make date up. Values are used as names of html-inputs 
	 * @var array
	 */
	protected $_components = array('day' => 'day', 'month' => 'month', 'year' => 'year');
	
	/**
	 * Constructs elements (all or some depending on _components)
	 * 
	 * @param array|Zend_Config $options 
	 */
	public function __construct($spec, $options = null)
	{
		// options
		$components = $this->_components;
		if( isset($options['components']) ) {
			$components = $options['components'];
			unset($options['components']);
		}
		$values = array();
		if( isset($options['value']) ) {
			$values = $options['value'];
			unset($options['value']);
		}

		// construct element
		parent::__construct($spec, $options);
		
		$errorsDecorator = $this->getDecorator('Errors');
		if( $errorsDecorator ) {
			$errorsDecorator->setOption('escape', false);
		}
		$this->setErrorMessageSeparator("</li>\n<li>");
		$this->setAutoInsertNotEmptyValidator(false);
		
		// construct option-elements (Multiselects)
		$optionsElements = array();
		foreach( $components as $component => $name ) {
			if( ! in_array($component, array_keys($this->_components)) ) {
				throw new Zend_Exception("Wrong date component '$component'");
			}
			$optionsElements[$component] = new Zend_Form_Element_Multiselect($name, array(
				'multiple' => false,
				'isArray' => false,
				'decorators' => array(
					'ViewHelper',
					'Label',
//					'Errors',
					array('HtmlTag', array('tag' => 'span'))
				),
				'label' => $component,
				'id' => (isset($options['id']) ? $options['id'] . '-' . $component : null)
			));
			// set multioptions
			$range = $this->_getRange($component);
			$optionsElements[$component]->setMultioptions($range);
			
			if( $this->_required ) {
				unset($range['0']);
			}
			$optionsElements[$component]->setValidators( array(
				array('InArray', true, array(
					'haystack' => array_keys($range),
					'messages' => array(
						Zend_Validate_InArray::NOT_IN_ARRAY => "Invalid $component"
					)
				))
			) );
			$optionsElements[$component]->setAutoInsertNotEmptyValidator(false);
			
			// set values
			if( isset($values[$component]) ) {
				$optionsElements[$component]->setValue($values[$component]);
			}
		}
		$this->setMultioptions($optionsElements);
		$this->_components = $components;
		
	}
	
	/**
	 * Build an array range of integers for day, month or year values
	 * 
	 * @param string $component 'day', 'month' or 'year'
	 * @return array 
	 */
	private function _getRange($component)
	{
		$ini = 1;
		switch($component) {
			case 'day':
				$end = 31;
				break;
			case 'month':
				$end = 12;
				break;
			case 'year':
				$ini = Zend_Date::now()->get(Zend_Date::YEAR);
				$end = $ini + 9;
				break;
			default:
				throw new Zend_Exception("Wrong date component '$component'");
		}
		$range = range($ini, $end);
		$range = array('0' => '--') + array_combine(
			array_values($range), array_values($range));
		if( $end < 100 ) {
			$range = array_map( function($v) {
				return is_integer($v) ? sprintf('%02d', $v) : $v;
			}, $range );
		}
		
		return $range;
	}
	
    /**
     * Pass setBelongsTo to each option element (necessary for decorator
	 *   FormElements)
     *
     * @param  string $array
     * @return Zend_Form_Element
     */
    public function setBelongsTo($array)
    {
		parent::setBelongsTo($array);
		
		foreach( $this->getMultiOptions() as $element ) {
			$element->setBelongsTo($array);
		}
		
        return $this;
    }
	
	/**
	 * Set values to each date component. $values is expected to be an array
	 *   which keys are component names (day, month or year).
	 * 
	 * @param array $values 
	 */
	public function setValue($values)
	{
		foreach( $this->_components as $component => $name ) {
			if( isset($values[$component]) ) {
				$this->getMultiOption($component)->setValue($values[$component]);
			}
		}
	}
	
	/**
	 * Validate Multidate by validating each component (multioption)
	 * 
	 * @param mixed $value
	 * @param array $context
	 * @return boolean 
	 */
	public function isValid($value, $context = null)
	{
		$valid = true;
		foreach( $this->getMultioptions() as $component => $element ) {
			$componentValue = isset($context[$element->getName()]) ?
				$context[$element->getName()] : $value;
			if( ! $element->isValid($componentValue, $context) )
			{
				// define messages
				foreach( $element->getMessages() as $error => $message ) {
					if( isset($this->_messages[$error]) ) {
						$this->_messages[$error] .= 
							$this->getErrorMessageSeparator() . $message;
					} else {
						$this->_messages[$error] = $message;
					}
					$this->_errors = array_merge($this->_errors, (array)$error);
				}
				// add erroneous class to elements
				if( isset($element->class) && strlen($element->class) > 0 ) {
					$element->class .= ' erroneous';
				} else {
					$element->class = 'erroneous';
				}
				
				$valid = false;
			}
		}
		
		return $valid;
	}
	
}
